#include <iostream>
#include <cstring>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <string>

#define COMMAND_SIZE 1024
#define FORMAT "[%s@%s >>> %s]# " 


#define MAX_ARGC 128

int g_argc = 0;
char* g_argv[MAX_ARGC];

char cwd[1024];
char cwdenv[1024];

// 退出码
int lastcode = 0;

#define MAX_ENVP 100

char* g_env[MAX_ENVP];
int g_envs = 0;

//获取环境变量表
void InitEnv()
{
    // 获取环境变量
    extern char** environ;
    memset(g_env, 0, sizeof g_env);
    for(int i = 0; environ[i]; ++i)
    {
        g_env[i] = (char*)malloc(strlen(environ[i])+1);
        strcpy(g_env[i], environ[i]);
        ++g_envs;
    }
    g_env[g_envs++] = (char*)"TEST=FOR_TEST";
    g_env[g_envs] = NULL;

    //导入环境变量
    for(int i = 0; g_env[i]; ++i)
    {
        putenv(g_env[i]);
    }

    environ = g_env;
}

//获取用户名
const char *GetUserName()
{
    const char* name = getenv("USER");
    return name==NULL?"None":name;
}

//获取主机名
const char *GetHostName()
{
    const char* hostname = getenv("HOATNAME");
    return hostname == NULL?"None":hostname;
}

//获取当前路径
const char* GetPwd()
{
    // const char* pwd = getenv("PWD"); // 通过cd命令带来的问题显示
    const char* pwd = getcwd(cwd, sizeof cwd);
    if(pwd != nullptr)
    {
        snprintf(cwdenv, sizeof cwdenv, "PWD+%s", pwd);
    }
    return pwd == NULL?"None":pwd;
}

//期间优化，我们的路径打印得有点长了，我们可以进行优化
std::string DirName(const char* pwd)
{
#define SLASH "/"
    std::string dir = pwd;
    if(dir == SLASH)
    {
        return SLASH;
    }
    auto pos = dir.rfind(SLASH);
    if(pos == std::string::npos) return "BUG?";
    return "~/" + dir.substr(pos+1);//+1是为了不想体现“/”，这里我算了
}

//获取家目录
const char* GetHome()
{
    const char* home = getenv("HOME");
    return home == NULL?"None":home;
}

//初始化命令行
bool MakeCommandLine(char cmd_prompt[], int size)
{
    snprintf(cmd_prompt, size, FORMAT, GetUserName(), GetHostName(),DirName(GetPwd()).c_str());
    return true;
}

//打印初始化好的命令行
void PrintCommandPrompt()
{
    char prompt[COMMAND_SIZE];
    MakeCommandLine(prompt, sizeof(prompt));
    printf("%s", prompt);
    fflush(stdout);
}

// 用户输入是否合法
bool GetCommandLine(char* out, int size)
{
    //fgets --- 读取一行 --- \n 也会读取到
    char* c = fgets(out, size, stdin);
    if(c == NULL)
    {
        //失败
        return 1;
    }
    // 为了避开我们输入的命令最后还输入了回车，我们可以在"\n"进行置"\0" -- 转义字符是占用一个字节的
    // sizeof计算的是整个数组的大小，包括字符串结尾的\0字符
    // strlen是一个运行时函数，用于计算字符串的长度，不包括结尾的\0字符。
    out[strlen(out)-1]='\0';
    // printf("%s\n", out);//回显测试 --- 有没有错误

    //细节处
    if(strlen(out) == 1)
    {
        return false;
    }
    return true;
}

// 命令行分析
bool CommandParse(char* commandline)
{
    #define SEP " "
    g_argc = 0;
    //"ls -a -l"->"ls" "-a" "-l"
    g_argv[g_argc++] = strtok(commandline, SEP);//截取了ls，strtok只能切一次，切成功的话，返回下一个字串的起始地址
    while(g_argv[g_argc++] = strtok(NULL, SEP));//继续分割剩余部分直到返回NULL;

    //细节 --- 修正g_arc --- 保证在最后一次分割不递增
    g_argc--;

    return g_argc>0?true:false;
}

bool Cd()
{

    if(g_argc == 1)
    {
        std::string home = GetHome();
        if(home == "None") return false;
        if(chdir(home.c_str()) != 0) return false;
    }
    else
    {
        std::string where = g_argv[1];
        if(where == "-")
        {
            //TODO
        }
        else if(where == "~")
        {
            std::string home = GetHome();
            if(chdir(home.c_str()) != 0) return false;
        }
        else
        {
            if(chdir(where.c_str()) != 0) return false;
        }
    }
    return true;
}

// 检测并处理内建命令
bool CheckAndExecBuiltin()
{
    std::string cmd = g_argv[0];
    if(cmd == "cd")
    {
        return Cd();
    }
    return false;
}

// 执行命令
int Execute()
{
    //创建子进程
    pid_t id = fork();
    if(id < 0)
    {
        return -1;
    }
    else if(id == 0)
    {
        //子进程
        execvp(g_argv[0], g_argv);
        exit(1);
    }
    //父进程
    else
    {
        int status = 0;
        pid_t rid = waitpid(id, &status, 0);
        //(void)rid;
        if(rid > 0)
        {
            lastcode = WEXITSTATUS(status);
        }
    }
    return 0;
}


int main()
{
    // 准备工作
    InitEnv();
    //命令行就是要不断读取我们的请求
    while(1)
    {
        PrintCommandPrompt();
        //用户输入
        //读取的命令
        char commandline[COMMAND_SIZE];
        if(!GetCommandLine(commandline,sizeof commandline))
        {
            continue;//有问题就继续输入
        }
        // 阶段测试:
        // printf("%s\n", commandline);


        // 开启命令的解析与执行
        
        // 命令行分析 --- 未来要使用程序替换，这种形式的参数，方便调用
        if(!CommandParse(commandline))
        {
            continue;
        }

        // 到这里就可以进行执行有效命令了！
        if(!CheckAndExecBuiltin())
        {
            Execute();
        }

        // 执行了一会发现：我们的内建命令没有实现 --- 要区分内建命令，然后分发给父进程，而不是子进程


    }
    return 0;
}